<template>
    <div class="hello">
        <h3>概要</h3>
        <h3>ts类，类型断言（用于 编码者 自己 指定具体类型），类型别名type，type定义函数类型 <span style="color: red">类型谓词 ？</span></h3>
        <h3>索引访问类型</h3>
        <hr>
    </div>
</template>

<script setup lang="ts">
    class People {
        readonly id:number=1  // 1 constructor传id 就覆盖id 不传默认就是1，且不能通过实例.id 修改值
        name:string
        age:number
        hobby: string[]
        money?:number
        // [propsName:string]:string 这里不能写
        constructor(name: string,age:number,hobby:string[],money?:number,id?:number,) { // 2 或者在构造函数里面赋初值
            this.name = name;
            this.age = age;
            this.hobby = hobby;
            this.money = money;
            if (id!==undefined) this.id = id
        }
        greet() {
            return "Hello, " + this.name;
        }
    }
    // let str:string = 'hello typescript'
    let me = new People("wf",20,["吃饭","睡觉"],undefined,undefined);
    console.log(me);
    me.greet()


    // 类型谓词 类型别名  基础类型  interface不能声明基础类型好像
    type Leixing = string | number | null
    // 对象类型
    type Person = {
        name:string
        age?:number
        hobby?:string[],
    }
    // type Person = {  // 报错
    //     name:string
    //     age?:number
    //     hobby?:string[],
    // }
    type Emp = {
        money:string
    }
    type arrtype = number[] | number


    const func = function (c1:Leixing,c2:Person,arr?:arrtype) {
        console.log(c1)
        console.log(c2)
        // console.log(arr?.length)
        console.log((arr as number[])?.length) // 我传的参数我断定它是number[]  否则就是不执行 即 undefined
        // console.log(<Array<number>>arr?.length)
    }
    func("123",{name:"wf",age:20},2)
    func("123",{name:"wf",age:20},[1,2,3])

    type kuozhan = Emp & Person // 类型别名的扩展   &前后可以是type 可以是接口

    /*type类型别名 也可以直接实例化*/
    const kz:kuozhan = {
        name:"wf",
        money:"123"
    }
    console.log("类型别名扩展 与实例化",kz)
    // type函数类型表达式  回调函数
    type GreetFunction = (str: string) => void  // 声明函数
    function greeter(fn: GreetFunction) {  // 传函数 参数为string  无返回值
        fn('Hello, World')
    }
    greeter((str)=>{
        console.log(str)
    })

    // 调用签名  (含有其他参数的)
    type GreetFunction2 = {
        namex:string
        (str: string) :void
    } // 声明对象
    function f(func:GreetFunction2){
        console.log(func.namex)
        func("123456namex")
    }
    function ss (str:string){
        console.log(str);
    }
    // 给函数加属性
    ss.namex="我是定义的函数"
    f(ss)
    // 构造签名
    interface SomeConstructor {
        new (name: string,age:number,hobby:string[],money?:number): People
        (n: number): number
    }
    function dosomething(people:SomeConstructor){
        if (people === People){
            return  new people("zs",20,["吃饭","睡觉"])
        }else{
            return people(100)
        }
    }
    //...
    // let p = dosomething(People)
    // console.log(p);
    // let p2 = dosomething((num:number)=>{
    //     console.log(num)
    //     return num
    // })
    
    // let p2:People|number = dosomething(People)
    // let p:People = dosomething(People)
    /* type 定义的对象里可以有函数、 in 属性存在判断*/
    type Fish = {
        name:string
        swim: () => void
    }
    type Bird = {
        name:string
        fly: () => void
    }
    type Human = {
        name:string
        swim?: () => void;
        fly?: () => void
    }

    function move(animal: Fish | Bird | Human) {
        // console.log(animal)
        if ("swim" in animal) { // in 属性存在判断
            // animal: Fish | Human  可能是人可能是鱼 使用类型断言
            return (animal as Fish).swim()
        }else{
            // animal: Bird | Human
            return (animal as Bird).fly()
        }
    }
    move({
        name:"zs",
        fly:()=>{
            console.log("fly")
        }
    })

    // 类型谓词
    // type Fish = { name: string,swim: () => void }
    // type Bird = { name: string,fly: () => void }

    //pet is Fish 类型谓词 作用仍然是将返回值 缩小至指定类型  (不仅会检查类型 还会检查属性)
    // 使用场景 多个interface 的交差类型 ts 会报错，Property 'xxx' does not exist on type 'Interface'.
    // 有 in typeof instanceof 以及 类型谓词的方式解决
    function isFish(pet: Fish | Bird): pet is Fish {
        // console.log((pet as Fish).swim !== undefined)
        return (pet as Fish).swim !== undefined
    }
    // 返回类型: Fish | Bird
    function getSmallPet(): Fish | Bird {
        let fish: Fish = {
            name: 'fishinstance',
            swim: () => {console.log("fishinstance")}
        }
        let bird: Bird = {
            name: 'birdinstance',
            fly: () => {console.log("birdinstance")}
        }
        return Math.floor(Math.random()*10)>5? bird : fish
    }

    let pet = getSmallPet()

    if (isFish(pet)) {
        pet.swim()  // 一定是条鱼
    } else {
        pet.fly()  // 一定是只鸟
    }
    const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()]
    console.log(zoo)
    const underWater1: Fish[] = zoo.filter(isFish)  //筛选出fish  写法1
    console.log(underWater1)
    const underWater2: Fish[] = zoo.filter(isFish) as Fish[]  // 筛选出fish  写法2
    console.log(underWater2)
    const underWater3: Fish[] = zoo.filter((pet): pet is Fish => { // 筛选出fish  写法3
        if (pet.name === 'frog') { // 自己的某些逻辑
            return false
        }
        return isFish(pet)
    })

    // 通用 类型谓词函数
    function isOfType<T>(varToBeChecked: any, propertyToCheckFor: keyof T): varToBeChecked is T {
        return (varToBeChecked as T)[propertyToCheckFor] !== undefined;
    }
    let pet2 = getSmallPet()
    if (isOfType<Fish>(pet2,"swim")) {
        console.log("鱼")
        pet2.swim()  // 一定是条鱼
    } else {
        console.log("鸟")
        pet2.fly()  // 一定是只鸟
    }

    let t1 = "t1"
    let t2 = 100
    if (isOfType<string>(t2,"length")) {
        console.log("string")
    } else {
        console.log("number")
    }
    /*控制语句的输出类型 可以改变原类型 的限制*/
    function example2() {
        let x: string | number | boolean
        x = Math.random() < 0.5
        console.log(x) //  boolean
        // 这里的if else 控制流 将覆盖x的Boolean 类型 导致下面 x2 = true赋值错误
        if (Math.random() < 0.5) {
            x = 'hello'   // x: string
        } else {
            x = 100   // x: number
        }
        return x    //number |string
    }

    let x2 = example2()
    x2 = 'hello'
    x2 = 100
    // x2 = true   // 由于出参只能是number |string 手动赋值 布尔则报错


    // 索引访问类型
    type Persons = { age: number, name: string, alive: boolean }
    type Age = Persons['age'] // number
    // let age: Age = '90'    //error

    interface Persons2 {name: string,age: number,alive: boolean}
    // type I1 = string | number
    type I1 = Persons2['age' | 'name']
    const i11: I1 = 100
    const i12: I1 = ''
    // const i13: I1 = true //error

    // type I2 = string | number | boolean
    type I2 = Persons[keyof Persons]
    const I21: I2 = ''
    const I22: I2 = 100
    const I23: I2 = true
    // const I24: I2 = {} // error


    const MyArray = [
        { name: 'Alice', age: 15 },
        { name: 'Bob', age: 23 },
        { name: 'Eve', age: 38 }
    ]
    type Persons3 = typeof MyArray[number] //拿到的是索引对象的类型  { name: string, age: number }
    const p:Persons3 = {name: 'xiaoqian', age: 11} //
    type Ages = typeof MyArray[number]['age']  //拿对象里某个类型 number   age这个 如果写成变量 需要用type 修饰
    const age: Ages = 123
</script>

<style scoped>

</style>
